Skip to content

Global styles: stop sanitize keeping breakpoints set on an element - #81312

Draft
ramonjd wants to merge 1 commit into
trunkfrom
fix/theme-json-sanitize-element-breakpoints
Draft

Global styles: stop sanitize keeping breakpoints set on an element#81312
ramonjd wants to merge 1 commit into
trunkfrom
fix/theme-json-sanitize-element-breakpoints

Conversation

@ramonjd

@ramonjd ramonjd commented Aug 7, 2026

Copy link
Copy Markdown
Member

What?

Low priority, data hygiene only. CSS output does not change.

sanitize() adds a breakpoint state to every element, so an element can carry @mobile or @tablet directly:

{
	"version": 3,
	"styles": {
		"elements": {
			"link": {
				"color": { "text": "black" },
				"@mobile": {
					"color": { "text": "orange" }
				}
			}
		},
		"blocks": {
			"core/group": {
				"elements": {
					"link": {
						"color": { "text": "blue" },
						"@mobile": {
							"color": { "text": "green" }
						}
					}
				},
				"@mobile": {
					"elements": {
						"link": {
							"color": { "text": "red" }
						}
					}
				}
			}
		}
	}
}

Orange and green are kept by the sanitizer and never rendered. Only red produces a media query, which is correct and stays that way.

Why?

The target structures are listed at lib/class-wp-theme-json-gutenberg.php:1277-1279, four lines above the loop this PR removes:

- top level elements:        styles.elements.link[':hover']
- block level elements:      styles.blocks['core/button'].elements.link[':hover']
- block responsive elements: styles.blocks['core/button']['@tablet'].elements.link[':hover']

A responsive element is a block breakpoint containing elements. An element carrying its own breakpoint is not one of the three, and node discovery never looks for it.

Because remove_insecure_properties() builds the same schema, the shape also survives untrusted input, so it can reach the database through user global styles and come back out of the REST API, while producing nothing.

The loop is not load-bearing for the shape that does work: blocks.<block>["@mobile"].elements is assembled at :1320 from the same element schema and does not need breakpoints nested inside each element.

How?

Delete the loop, leaving a comment in its place explaining where responsive elements are actually assembled.

Testing Instructions

Use this theme.json

{
	"$schema": "../../schemas/json/theme.json",
	"version": 3,
	"styles": {
		"elements": {
			"link": {
				"color": { "text": "#0000ff" },
				":hover": {
					"color": { "text": "#000088" }
				},
				"@mobile": {
					"color": { "text": "#ff9900" }
				}
			}
		},
		"blocks": {
			"core/group": {
				"elements": {
					"link": {
						"color": { "text": "#008800" },
						"@mobile": {
							"color": { "text": "#ff9900" }
						}
					}
				},
				"@mobile": {
					"elements": {
						"link": {
							"color": { "text": "#ff0000" }
						}
					}
				}
			},
			"core/button": {
				":hover": {
					"color": { "background": "#333333" }
				},
				"@mobile": {
					":hover": {
						"color": { "background": "#666666" }
					}
				}
			}
		}
	}
}

Both #ff9900 declarations are the dead shapes and they should be marked "invalid" by the local schema. The CSS shouldn't appear, but they're included just to make sure!

Here's some example HTML

<!-- wp:paragraph -->
<p>Paragraph with <a href="https://wordpress.org">link</a></p>
<!-- /wp:paragraph -->

<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group"><!-- wp:paragraph -->
<p>Paragraph with <a href="https://wordpress.org" data-type="link" data-id="https://wordpress.org">link</a> in a Group block</p>
<!-- /wp:paragraph --></div>
<!-- /wp:group -->

Check:

  1. In the editor and frontend, check the hover/device states and ensure they match the valid theme.json entries
  2. Site Editor → Styles → Elements → Link: the colour control shows blue, and the panel loads without error.
  3. Save a change in the above Site Editor Styles, reload, confirm they persist
Kapture.2026-08-07.at.16.46.06.mp4

Related

#81253 makes the schema reject the same two paths, so the editor flags them while typing.

`sanitize()` added a breakpoint state to every element, so an element
could carry `@mobile` or `@tablet` directly, at the top level and inside
a block. Nothing reads that shape. The target structures are listed four
lines above the loop, and the responsive one puts the breakpoint on the
block with `elements` inside it.

The data survived as far as the database and the REST API, since
`remove_insecure_properties()` uses the same schema, while never
producing a rule.

Drop the loop. CSS output is unchanged, which the existing rendering
tests cover, and the two structures that do work are assembled elsewhere
from this same element schema.

Two sanitization tests now assert the breakpoints are stripped rather
than kept, at block and top level.
@ramonjd
ramonjd force-pushed the fix/theme-json-sanitize-element-breakpoints branch from 2ead2d8 to 60bc9ca Compare August 7, 2026 06:33
* @covers WP_Theme_JSON_Gutenberg::remove_insecure_properties
*/
public function test_remove_insecure_properties_preserves_responsive_block_element_styles() {
public function test_remove_insecure_properties_strips_breakpoints_set_on_a_block_element() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inverts an existing assertion. test_remove_insecure_properties_preserves_responsive_block_element_styles asserted the element-nested breakpoints were kept.

Is this change right or is the original correct?

@ramonjd ramonjd added [Type] Code Quality Issues or PRs that relate to code quality Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json labels Aug 7, 2026
@ramonjd ramonjd self-assigned this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Code Quality Issues or PRs that relate to code quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant